package cc.fine.svc.main.service.recommendation;

import cc.fine.svc.config.redis.utils.RedisCache;
import cc.fine.svc.main.bean.po.RelUserFan;
import cc.fine.svc.main.service.RelUserVideoHistoryService;
import cc.fine.svc.main.service.WeightService;
import cc.fine.svc.main.service.rel.RelUserFanService;
import cc.fine.svc.main.service.tbl.TblVideoService;
import cc.fine.svc.main.utils.EsUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @AUTHOR: wyihe
 * @DATE: 2023/11/6
 */
@Service
public class RecommendationServiceImpl implements RecommendationService {

    @Autowired
    private RedisCache redisCache;

    @Autowired
    private EsUtil esUtil;

    @Autowired
    private TblVideoService videoService;

    @Autowired
    private RelUserFanService fanService;

    @Override
    public List<Map<String, Object>> recommendation(String userId, Integer n) {
        // 处理用户权重
        Map<String, Long> weightMap = redisCache.getCacheMapValue(WeightService.Weight_Map_Cache, userId);
        if (weightMap == null) weightMap = new HashMap<>();
        ArrayList<Map.Entry<String, Long>> list = new ArrayList<>(weightMap.entrySet());

        list.sort((o1, o2) -> {
            if (o1.getValue() >= o2.getValue()) return -1;
            return 1;
        });

        StringBuilder tags = new StringBuilder();
        for (int i = 0; i < Math.min(10, list.size()); ++i) {
            tags.append(list.get(i).getKey()).append(" ");
        }

        // 查询
        List<String> videoHistory = redisCache.getCacheMapValue(RelUserVideoHistoryService.History_Record_map, userId);
        if (videoHistory == null) videoHistory = new ArrayList<>();
        HashSet<String> idsSet = new HashSet<>(videoHistory);

        List<Map<String, Object>> res = new ArrayList<>();
        for (int i = 0; ; ++i) {
            List<Map<String, Object>> temp = search(tags.toString(), i, n);

            // 排除已经看过的视频，加入没看过的视频
            Iterator<Map<String, Object>> iterator = temp.iterator();
            while (iterator.hasNext()) {
                Map<String, Object> next = iterator.next();
                Map<String, Object> video = (Map<String, Object>) next.get("video");
                if (video == null) continue;
                String videoId = (String) video.get("id");

                if (idsSet.contains(videoId)) {
                    iterator.remove();
                }
            }

            // 将剩余没看过的加入res
            res.addAll(temp);

            // 如果查完了或者res数量够了，就退出循环
            if (temp.isEmpty() || res.size() == n) {
                break;
            }
        }

        // 如果查完了res还没到，试着把其他条件的没看过的视频加入进去
        if (res.size() < n) {
            List<Map<String, Object>> temp = searchNoIds(videoHistory, 0, n - res.size());
            res.addAll(temp);
        }

        return res;
    }

    @Override
    public List<Map<String, Object>> recommendationForIp(String ip, String userId, Integer n) {

        // 查询
        List<String> videoHistory = redisCache.getCacheMapValue(RelUserVideoHistoryService.History_Record_map, userId);
        if (videoHistory == null) videoHistory = new ArrayList<>();
        HashSet<String> idsSet = new HashSet<>(videoHistory);

        List<Map<String, Object>> res = new ArrayList<>();
        for (int i = 0; ; ++i) {
            List<Map<String, Object>> temp = searchForIp(ip, i, n);

            // 排除已经看过的视频，加入没看过的视频
            Iterator<Map<String, Object>> iterator = temp.iterator();
            while (iterator.hasNext()) {
                Map<String, Object> next = iterator.next();
                Map<String, Object> video = (Map<String, Object>) next.get("video");
                if (video == null) continue;
                String videoId = (String) video.get("id");

                if (idsSet.contains(videoId)) {
                    iterator.remove();
                }
            }

            // 将剩余没看过的加入res
            res.addAll(temp);

            // 如果查完了或者res数量够了，就退出循环
            if (temp.isEmpty() || res.size() == n) {
                break;
            }
        }

        return res;
    }

    @Override
    public List<Map<String, Object>> recommendationForTag(List<String> tags, String userId, Integer n) {
        // 封装 tags
        StringBuilder sb = new StringBuilder();
        tags.forEach(x -> sb.append(x).append(" "));

        // 查询
        List<String> videoHistory = redisCache.getCacheMapValue(RelUserVideoHistoryService.History_Record_map, userId);
        if (videoHistory == null) videoHistory = new ArrayList<>();
        HashSet<String> idsSet = new HashSet<>(videoHistory);

        List<Map<String, Object>> res = new ArrayList<>();
        for (int i = 0; ; ++i) {
            List<Map<String, Object>> temp = searchForTag(sb.toString() , i, n);

            // 排除已经看过的视频，加入没看过的视频
            Iterator<Map<String, Object>> iterator = temp.iterator();
            while (iterator.hasNext()) {
                Map<String, Object> next = iterator.next();
                Map<String, Object> video = (Map<String, Object>) next.get("video");
                if (video == null) continue;
                String videoId = (String) video.get("id");

                if (idsSet.contains(videoId)) {
                    iterator.remove();
                }
            }

            // 将剩余没看过的加入res
            res.addAll(temp);

            // 如果查完了或者res数量够了，就退出循环
            if (temp.isEmpty() || res.size() == n) {
                break;
            }
        }

        return res;
    }

    @Override
    public List<Map<String, Object>> recommendationForFan(String userId, Integer page,Integer n) {
        // 获得自己关注的人的id
        QueryWrapper<RelUserFan> w = new QueryWrapper<>();
        w.eq("follower_id", userId);
        List<String> ids = fanService.list(w).stream()
                .map(x -> x.getMasterId())
                .collect(Collectors.toList());

        return searchForFan(ids, page, n);
    }

    @Override
    public List<Map<String, Object>> recommendationNoLogin(Integer page, Integer limit) {


        SearchSourceBuilder builder = new SearchSourceBuilder();
        builder.sort("count.favorCount", SortOrder.DESC);

        try {
            List<Map<String, Object>> res = esUtil.searchListData(TblVideoService.Es_Index, builder, limit, page * limit, "", "", "");
            Collections.shuffle(res);
            return res;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private List<Map<String, Object>> searchForFan(List<String> ids, int page, Integer limit) {

        SearchSourceBuilder builder = new SearchSourceBuilder();
        builder.query(QueryBuilders.termsQuery("user.id.keyword", ids))
        ;

        try {
            List<Map<String, Object>> res = esUtil.searchListData(TblVideoService.Es_Index, builder, limit, page * limit, "", "", "");
            Collections.reverse(res);
            return res;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

    }

    private List<Map<String, Object>> searchForTag(String tags, int page, Integer limit) {

        SearchSourceBuilder builder = new SearchSourceBuilder();
        builder
                .query(QueryBuilders.matchQuery("tagStr", tags).analyzer("ik_smart"));

        try {
            List<Map<String, Object>> res = esUtil.searchListData(TblVideoService.Es_Index, builder, limit, page * limit, "", "", "");

            return res;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private List<Map<String, Object>> searchForIp(String ip, int page, int limit) {
        SearchSourceBuilder builder = new SearchSourceBuilder();
        builder
                .query(QueryBuilders.matchQuery("localMom.keyword", ip));

        try {
            List<Map<String, Object>> res = esUtil.searchListData(TblVideoService.Es_Index, builder, limit, page * limit, "", "", "");
            return res;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private List<Map<String, Object>> search(String tags, int page, int limit) {
        SearchSourceBuilder builder = new SearchSourceBuilder();
        builder.query(
                        QueryBuilders.boostingQuery(
                                        QueryBuilders.matchQuery("tagStr", tags).analyzer("ik_smart"),
                                        QueryBuilders.matchQuery("tagStr", "&@").analyzer("ik_smart")
                                )
                                .negativeBoost(0.7f)
                );

        try {
            List<Map<String, Object>> res = esUtil.searchListData(TblVideoService.Es_Index, builder, limit, page * limit, "", "", "");
            return res;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private List<Map<String, Object>> searchNoIds(List<String> ids, Integer page, Integer limit) {
        SearchSourceBuilder builder = new SearchSourceBuilder();
        builder.query(
                QueryBuilders
                        .boolQuery()
                        .mustNot(
                                QueryBuilders.termsQuery("video.id", ids.toArray())
                        )
        );

        try {
            List<Map<String, Object>> res = esUtil.searchListData(TblVideoService.Es_Index, builder, limit, page * limit, "", "", "");
            return res;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}
